package com.deng.eventflow.core.tmf;

import com.deng.eventflow.core.Processor;
import com.deng.eventflow.core.exception.Variant;
import com.deng.eventflow.core.petitpoucet.DirectValue;
import com.deng.eventflow.core.petitpoucet.NodeFunction;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;

public class QueueSource extends Source {

    protected List<Object> m_events;

    protected boolean m_loop = true;

    protected int m_index;

    public QueueSource(int arity) {
        super(arity);
        m_events = new ArrayList<Object>();
        m_index = 0;
    }

    public QueueSource() {
        super(1);
        m_events = new ArrayList<Object>();
        m_index = 0;
    }

    public void setEvents(Collection<Object> queue) {
        for (Object o : queue) {
            m_events.add(o);
        }
    }

    public QueueSource setEvents(Object ... queue)
    {
        for (Object o : queue)
        {
            m_events.add(o);
        }
        return this;
    }

    public QueueSource addEvent(Object e)
    {
        m_events.add(e);
        return this;
    }


    public QueueSource loop(boolean b)
    {
        m_loop = b;
        return this;
    }



    @Override
    public Processor duplicate(boolean with_state) {
        {
            QueueSource out = new QueueSource(getOutputArity());
            out.m_loop = m_loop;
            out.setEvents(m_events);
            if (with_state)
            {
                out.m_index = m_index;
            }
            return out;
        }
    }

    @Override
    protected boolean compute(Object[] inputs, Queue<Object[]> outputs) {
        int size = m_events.size();
        if (m_index >= size)
        {
            return false;
        }
        Object[] output = new Object[getOutputArity()];
        Object event = m_events.get(m_index);
        if (m_loop)
        {
            m_index = (m_index + 1) % size;
        }
        else
        {
            // If we don't loop, play the events only once
            m_index++;
        }
        if (m_index > size && !m_loop)
        {
            // No more events from this queue
            return false;
        }
        for (int i = 0; i < getOutputArity(); i++)
        {
            if (event == null)
            {
                // If one of the elements is null, don't output anything
                return true;
            }
            output[i] = event;
        }
        outputs.add(output);
        if (m_eventTracker != null)
        {
            int index = m_index - 1;
            if (index < 0)
            {
                index += size;
            }
            for (int i = 0; i < getOutputArity(); i++)
            {
                associateTo(new QueueFunction(getId(), index), i, m_outputCount);
            }
        }
        m_outputCount++;
        return true;
    }


    @Override
    public void reset() {
        super.reset();
        m_index = 0;
    }

    @Override
    public Class<?> getOutputType(int index) {
        // We return the type of the first non-null object in the queue
        for (Object o : m_events)
        {
            if (o != null)
            {
                return o.getClass();
            }
        }
        return Variant.class;
    }


    public static class QueueFunction implements NodeFunction {
        protected int m_queueIndex = 0;

        protected int m_processorId = 0;

        /**
         * Creates a new QueueFunction
         * @param proc_id The id of the processor
         * @param index The position of the event in the queue
         */
        public QueueFunction(int proc_id, int index)
        {
            super();
            m_processorId = proc_id;
            m_queueIndex = index;
        }

        @Override
        public String getDataPointId()
        {
            return "BP" + m_processorId + ".Q." + m_queueIndex;
        }

        @Override
        public String toString()
        {
            return getDataPointId();
        }

        @Override
        public NodeFunction dependsOn()
        {
            return DirectValue.instance;
        }

        public int getIndex()
        {
            return m_queueIndex;
        }
    }

}
